/**************************************************************
	CTRNN
	use addNeuron() and addSynapse()
	call update() to process
	access neuron data with getNeuronOutput()
 **************************************************************/

#ifndef __DALELIB_ALIFE_CTRNN_H__
#define __DALELIB_ALIFE_CTRNN_H__

//#include <dalelib/maths.h>
//#include <dalelib/graphics.h>
//#include <dalelib/utils/daleassert.h>
#include <vector>
#include <fstream>
using namespace std;

namespace dalelib
{

class CTRNNNeuron;

class CTRNNSynapse
{
protected:

	friend class CTRNN;

	CTRNNNeuron* _from;
	float _weight;

public:

	CTRNNSynapse(CTRNNNeuron* _From,const float _Weight)
		: _from(_From)
		, _weight(_Weight)
	{ }

};

//**************************************************************
class CTRNNNeuron
{
protected:

	friend class CTRNN;

	float _activation;
	float _output; // *** firing rate ***
	float _input_sum;
	float _tau;
	float _bias;
	float _param;
	v2 _pos;
	vector<CTRNNSynapse> _synapses;

public:

	CTRNNNeuron()
		: _activation(0.f)
		, _output(0.f)
		, _input_sum(0.f)
		, _tau(10.f)
		, _bias(0.f)
	{ }

	vector<CTRNNSynapse>& getSynapses() { return _synapses; }
	float& getTau() { return _tau; }

};

//**************************************************************
class CTRNN
{
public:

	typedef vector<CTRNNNeuron*> NeuronList;

private:

	NeuronList _neurons;

public:

	CTRNN()
	{ }

	~CTRNN()
	{
		while(!_neurons.empty()) { deleteNeuron(0); }
	}

	CTRNNNeuron* getNeuron(const int _Index) { return _neurons[_Index]; }
	vector<CTRNNNeuron*>& getNeurons() { return _neurons; }

	void deleteNeuron(const unsigned int _Index)
	{
		if(_Index<_neurons.size())
		{
			delete _neurons[_Index];
			_neurons.erase(_neurons.begin()+_Index);
		}
	}

	void injectInput(const unsigned int _Index,const float _Value)
	{
		if(_Index<_neurons.size())
		{
			_neurons[_Index]->_input_sum += _Value;
		}
	}

	void setOutput(const unsigned int _Index,const float _Value)
	{
		if(_Index<_neurons.size())
		{
			_neurons[_Index]->_output = _Value;
		}
	}

	float extractOutput(const unsigned int _Index)
	{
		return (_Index<_neurons.size()) ? _neurons[_Index]->_output : 0.f;
	}

	int addSynapse(CTRNNNeuron* _From,const int _To,const float _Weight)
	{
		_neurons[_To]->_synapses.push_back(CTRNNSynapse(_From,_Weight));
		return _neurons[_To]->_synapses.size()-1;
	}

	int addNeuron()
	{
		_neurons.push_back(new CTRNNNeuron());
		return _neurons.size()-1;
	}

	void calculateCentreCrossingBiases()
	{
		for(unsigned int i=0; i<_neurons.size(); ++i)
		{
			_neurons[i]->_bias = 0.f;
			for(unsigned int j=0; j<_neurons[i]->_synapses.size(); ++j)
			{
				_neurons[i]->_bias += _neurons[i]->_synapses[j]._weight;
			}
			_neurons[i]->_bias *= -0.5f;
		}
	}

	void update()
	{
		// *** accumulate weighted inputs across network ***
		for(unsigned int i=0; i<_neurons.size(); ++i)
		{
			for(unsigned int j=0; j<_neurons[i]->_synapses.size(); ++j)
			{
				_neurons[i]->_input_sum += _neurons[i]->_synapses[j]._from->_output*_neurons[i]->_synapses[j]._weight;
			}
		}

		// *** calculate all activations and outputs ***
		for(unsigned int i=0; i<_neurons.size(); ++i)
		{
			_neurons[i]->_activation += (_neurons[i]->_input_sum-_neurons[i]->_activation)/_neurons[i]->_tau;
			const float a = _neurons[i]->_activation+_neurons[i]->_bias;
//			_neurons[i]->_output = 1.f/(1.f+exp(-a)); // *** logistic (sigmoid) ***
			float e1 = exp(a), e2 = exp(-a); _neurons[i]->_output = (e1-e2)/(e1+e2); // *** hyperbolic (tanh) ***
			_neurons[i]->_input_sum = 0.f; // *** clear neurons' input ***
		}
	}

};

} // namespace dalelib

#endif // __DALELIB_ALIFE_CTRNN_H__
